/*
 *  ppui/GraphicsAbstract.h
 *
 *  Copyright 2009 Peter Barth
 *
 *  This file is part of Milkytracker.
 *
 *  Milkytracker is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  Milkytracker is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with Milkytracker.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#ifndef __GRAPHICSABSTRACT_H__
#define __GRAPHICSABSTRACT_H__

#include "BasicTypes.h"

class PPFont;

#define __PPGRAPHICSLINETEMPLATE \
    if (x1 > currentClipRect.x2 || \
        x2 < currentClipRect.x1 || \
        y1 > currentClipRect.y2 || \
        y2 < currentClipRect.y1) \
        return; \
    clipLine(x1, y1, x2, y2); \
    if (x2 < x1) \
        return; \
    pp_int32 i, deltax, deltay, numpixels; \
    pp_int32 d, dinc1, dinc2; \
    pp_int32 x, xinc1, xinc2; \
    pp_int32 y, yinc1, yinc2; \
    /* Calculate deltax and deltay for initialisation */ \
    deltax = abs(x2 - x1); \
    deltay = abs(y2 - y1); \
    /* Initialize all vars based on which is the independent variable */ \
    if (deltax >= deltay) \
    { \
        /* x is independent variable */ \
        numpixels = deltax + 1; \
        d = (2 * deltay) - deltax; \
        dinc1 = deltay << 1; \
        dinc2 = (deltay - deltax) << 1; \
        xinc1 = 1; \
        xinc2 = 1; \
        yinc1 = 0; \
        yinc2 = 1; \
    } \
    else \
    { \
        /* y is independent variable */ \
        numpixels = deltay + 1; \
        d = (2 * deltax) - deltay; \
        dinc1 = deltax << 1; \
        dinc2 = (deltax - deltay) << 1; \
        xinc1 = 0; \
        xinc2 = 1; \
        yinc1 = 1; \
        yinc2 = 1; \
    } \
    /* Make sure x and y move in the right directions */ \
    if (x1 > x2) \
    { \
        xinc1 = -xinc1; \
        xinc2 = -xinc2; \
    } \
    if (y1 > y2) \
    { \
        yinc1 = -yinc1; \
        yinc2 = -yinc2; \
    } \
    /* Start drawing at */ \
    x = x1; \
    y = y1; \
    /* Draw the pixels */ \
    for (i = 1; i <= numpixels; i++) \
    { \
        setPixel(x, y); \
        if (d < 0) \
        { \
            d = d + dinc1; \
            x = x + xinc1; \
            y = y + yinc1; \
        } \
        else \
        { \
            d = d + dinc2; \
            x = x + xinc2; \
            y = y + yinc2; \
        } \
    }

#define __PPGRAPHICSAALINETEMPLATE \
    if (x1 > currentClipRect.x2 || \
        x2 < currentClipRect.x1 || \
        y1 > currentClipRect.y2 || \
        y2 < currentClipRect.y1) \
        return; \
    clipLine(x1, y1, x2, y2); \
    if (x2 < x1) \
        return; \
    PPColor* col = &currentColor; \
    pp_int32 x, y, inc; /* these must be >=32 bits */ \
    pp_int32 dx, dy; \
    dx = (x2 - x1); \
    dy = (y2 - y1); \
    if (abs(dx) > abs(dy)) { \
        if (dx < 0) { \
            dx = -dx; \
            dy = -dy; \
            swap(&x1, &x2); \
            swap(&y1, &y2); \
        } \
        x = x1 << 16; \
        y = y1 << 16; \
        inc = (dx == 0 ? 0 : (dy * 65536) / dx); \
        while ((x >> 16) < x2) { \
            set_pixel_transp(this, x >> 16, y >> 16, \
                             col, (y >> 8) & 0xFF); \
            set_pixel_transp(this, x >> 16, (y >> 16) + 1, \
                             col, (~y >> 8) & 0xFF); \
            x += (1 << 16); \
            y += inc; \
        } \
    } else{ \
        if (dy < 0) { \
            dx = -dx; \
            dy = -dy; \
            swap(&x1, &x2); \
            swap(&y1, &y2); \
        } \
        x = x1 << 16; \
        y = y1 << 16; \
        inc = (dy == 0 ? 0 : (dx * 65536) / dy); \
        while ((y >> 16) < y2) { \
            set_pixel_transp(this, x >> 16, y >> 16, \
                             col, (x >> 8) & 0xFF); \
            set_pixel_transp(this, (x >> 16) + 1, (y >> 16), \
                             col, (~x >> 8) & 0xFF); \
            x += inc; \
            y += (1 << 16); \
        } \
    }

class PPGraphicsAbstract
{
    protected:
        static void swap(pp_int32* a, pp_int32* b)
        {
            pp_int32 h = *a;
            *a = *b;
            *b = h;
        }

        pp_int32 width, height;
    public:
        bool lock;
    protected:
        PPColor currentColor;
        pp_uint16 color16;
        PPRect currentClipRect, origRect;

        PPFont* currentFont;

        virtual void validateRect()
        {
            if ((currentClipRect.x1 < 0 && currentClipRect.x2 < 0) ||
                (currentClipRect.y1 < 0 && currentClipRect.y2 < 0) ||
                (currentClipRect.x1 > width && currentClipRect.x2 > width) ||
                (currentClipRect.y1 > height && currentClipRect.y2 > height))
            {
                currentClipRect.x1 = 0;
                currentClipRect.x2 = 0;
                currentClipRect.y1 = 0;
                currentClipRect.y2 = 0;
                return;
            }

            if (currentClipRect.x1 < 0)
                currentClipRect.x1 = 0;
            if (currentClipRect.y1 < 0)
                currentClipRect.y1 = 0;

            if (currentClipRect.x2 > width)
                currentClipRect.x2 = width;
            if (currentClipRect.y2 > height)
                currentClipRect.y2 = height;
        }

        void convertColorTo16()
        {
            color16 = (((pp_uint16)((currentColor.r) >> 3) << 11) + ((pp_uint16)((currentColor.g) >> 2) << 5) + (pp_uint16)((currentColor.b) >> 3));
        }

        PPGraphicsAbstract (pp_int32 w, pp_int32 h) :
            width(w), height(h),
            lock(false),
            currentFont(NULL)
        {
        }
    public:
        pp_int32 getWidth() {
            return width;
        }
        pp_int32 getHeight() {
            return height;
        }

        void setRect(pp_int32 x1, pp_int32 y1, pp_int32 x2, pp_int32 y2)
        {
            currentClipRect.x1 = x1; currentClipRect.y1 = y1; currentClipRect.x2 = x2; currentClipRect.y2 = y2;
            origRect = currentClipRect;
            validateRect();
        }

        void setRect(PPRect r)
        {
            currentClipRect = origRect = r;
            validateRect();
        }

        PPRect getRect() {
            return origRect;
        }

        void setFullRect()
        {
            currentClipRect.x1 = 0;
            currentClipRect.y1 = 0;
            currentClipRect.x2 = width - 1;
            currentClipRect.y2 = height - 1;
            origRect = currentClipRect;
            validateRect();
        }

        void clipLine(pp_int32& x1, pp_int32& y1, pp_int32& x2, pp_int32& y2)
        {
            if (x1 < currentClipRect.x1)
            {
                float stepy = (float)(y2 - y1) / (float)(x2 - x1);

                y1 = (pp_int32)((float)y1 + stepy * (currentClipRect.x1 - x1));
                x1 = currentClipRect.x1;
            }

            if (x2 > currentClipRect.x2)
            {
                float stepy = (float)(y2 - y1) / (float)(x2 - x1);

                x2 = currentClipRect.x2;
                y2 = (pp_int32)((float)y1 + stepy * (x2 - x1));
            }
        }

        void setColor(pp_int32 r, pp_int32 g, pp_int32 b) {
            currentColor.r = r; currentColor.g = g; currentColor.b = b; convertColorTo16();
        }
        void setColor(const PPColor& color) {
            currentColor = color;  convertColorTo16();
        }
        void setSafeColor(pp_int32 r, pp_int32 g, pp_int32 b)
        {
            if (r > 255) r = 255;
            if (g > 255) g = 255;
            if (b > 255) b = 255;
            currentColor.r = r; currentColor.g = g; currentColor.b = b; convertColorTo16();
        }

        PPColor getColor() {
            return currentColor;
        }

        virtual void setFont(PPFont* font) {
            currentFont = font;
        }
        PPFont * getCurrentFont() {
            return currentFont;
        }

        // Interfaces to be implemented by sub class
        virtual void setPixel(pp_int32 x, pp_int32 y) = 0;
        virtual void setPixel(pp_int32 x, pp_int32 y, const PPColor& color) = 0;

        virtual void fill(PPRect r) = 0;
        virtual void fill() = 0;

        virtual void drawHLine(pp_int32 x1, pp_int32 x2, pp_int32 y) = 0;
        virtual void drawVLine(pp_int32 y1, pp_int32 y2, pp_int32 x) = 0;

        virtual void drawLine(pp_int32 x1, pp_int32 y1, pp_int32 x2, pp_int32 y2) = 0;
        virtual void drawAntialiasedLine(pp_int32 x1, pp_int32 y1, pp_int32 x2, pp_int32 y2) = 0;

        virtual void blit(const pp_uint8* src, const PPPoint& p, const PPSize& size,
                          pp_uint32 pitch, pp_uint32 bpp, pp_int32 intensity = 256) = 0;

        virtual void drawChar(pp_uint8 chr, pp_int32 x, pp_int32 y, bool underlined = false) = 0;
        virtual void drawString(const char* str, pp_int32 x, pp_int32 y, bool underlined = false) = 0;
        virtual void drawStringVertical(const char* str, pp_int32 x, pp_int32 y, bool underlined = false) = 0;

        virtual void fillVerticalShaded(PPRect r, const PPColor& colSrc, const PPColor& colDst, bool invertShading)
        {
            const pp_int32 height = (r.y2 - r.y1);

            if (!height)
                return;

            pp_int32 addr = (colDst.r - colSrc.r) * 65536 / height;
            pp_int32 addg = (colDst.g - colSrc.g) * 65536 / height;
            pp_int32 addb = (colDst.b - colSrc.b) * 65536 / height;

            if (invertShading)
            {
                addr = -addr;
                addg = -addg;
                addb = -addb;
            }

            pp_int32 rd = (invertShading ? colDst.r : colSrc.r) * 65536;
            pp_int32 gr = (invertShading ? colDst.g : colSrc.g) * 65536;
            pp_int32 bl = (invertShading ? colDst.b : colSrc.b) * 65536;

            const pp_int32 y1 = r.y1;
            const pp_int32 y2 = r.y2;
            for (pp_int32 i = y1; i < y2; i++)
            {
                setColor(rd >> 16, gr >> 16, bl >> 16);
                drawHLine(r.x1, r.x2, i);
                rd += addr; gr += addg; bl += addb;
            }
        }

        virtual void fillVerticalShaded(const PPColor& colSrc, const PPColor& colDst, bool invertShading)
        {
            fillVerticalShaded(currentClipRect, colSrc, colDst, invertShading);
        }
};

#endif